#ifndef napyr2_Module_H
#define napyr2_Module_H

// *****************************************************************************************
//
// NAPYR2 (http://p-nand-q.com/python/napyr.html)
//
// Copyright (c) 2014, Gerson Kurz
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice, this
//    list of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its contributors may
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// *****************************************************************************************

#include "napyr2.h"

#include <string>
#include <vector>
#include <map>

#include "napyr2_Object.h"



namespace napyr2 // begins here
{
#pragma warning( push )
#pragma warning( disable: 4251 )
#pragma warning( disable: 4127 )


typedef PythonObject* (*LPFN_void)();
typedef PythonObject* (*LPFN_s)(const char*);
typedef PythonObject* (*LPFN_O)(const PythonObject&);
typedef PythonObject* (*LPFN_OO)(const PythonObject&, const PythonObject&);
typedef PythonObject* (*LPFN_OOO)(const PythonObject&, const PythonObject&, const PythonObject&);

class NAPYR2_API InvokeBase
{
public:
	InvokeBase(const char* name)
	{
		printf("'%s' created at 0x%08lx\r\n", name,this);
	}
	virtual PyObject* Callback(PyObject* self, PyObject* args) = 0;

protected:
	PyObject* CallbackResult(PythonObject* po);
};

template <typename T> class Invoke : public InvokeBase
{
public:
	Invoke<T>(T callback)
	{
	}

	PyObject* Callback(PyObject* self, PyObject* args)
	{
		// default: not implemented
		return NULL;
	}
};

template <> class Invoke<LPFN_O> : public InvokeBase
{
public:
	Invoke(LPFN_O callback)
		:	m_callback( callback ),
			InvokeBase("Invoke<LPFN_O>")
	{
	}

	PyObject* Callback(PyObject* self, PyObject* args)
	{
		PyObject* O[1];
    
		if (!PyArg_ParseTuple(args, "O", &O[0]))
			return NULL;

		PythonObject p[1] = {
			PythonObject(O[0])
		};

		PythonObject* result = m_callback(p[0]);
		Py_DECREF(O[0]);		
		return CallbackResult(result);
	}

private:
	LPFN_O m_callback;
};

template <> class Invoke<LPFN_s> : public InvokeBase
{
public:
	Invoke(LPFN_s callback)
		:	m_callback( callback ),
			InvokeBase("Invoke<LPFN_s>")
	{
	}

	PyObject* Callback(PyObject* self, PyObject* args)
	{
		const char* s;
    
		if (!PyArg_ParseTuple(args, "s", &s))
			return NULL;

		PythonObject* result;
		Py_BEGIN_ALLOW_THREADS
		result = m_callback(s);
		Py_END_ALLOW_THREADS
		return CallbackResult(result);
	}

private:
	LPFN_s m_callback;
};

template <> class Invoke<LPFN_void> : public InvokeBase
{
public:
	Invoke(LPFN_void callback)
		:	m_callback( callback ),
			InvokeBase("Invoke<LPFN_void>")
	{
	}

	PyObject* Callback(PyObject* self, PyObject* args)
	{
		PythonObject* result;
		Py_BEGIN_ALLOW_THREADS
		result = m_callback();
		Py_END_ALLOW_THREADS
		return CallbackResult(result);
	}

private:
	LPFN_void m_callback;
};

template <> class Invoke<LPFN_OO> : public InvokeBase
{
public:
	Invoke(LPFN_OO callback)
		:	m_callback( callback ),
			InvokeBase("Invoke<LPFN_OO>")
	{
	}

	PyObject* Callback(PyObject* self, PyObject* args)
	{
		PyObject* O[2];
    
		if (!PyArg_ParseTuple(args, "OO", &O[0], &O[1]))
			return NULL;

		PythonObject p[2] = {
			PythonObject(O[0]),
			PythonObject(O[1]),
		};

		PythonObject* result;
		Py_BEGIN_ALLOW_THREADS
		result = m_callback(p[0], p[1]);
		Py_END_ALLOW_THREADS
		Py_DECREF(O[0]);
		Py_DECREF(O[1]);
		return CallbackResult(result);
	}

private:
	LPFN_OO m_callback;
};


template <> class Invoke<LPFN_OOO> : public InvokeBase
{
public:
	Invoke(LPFN_OOO callback)
		:	m_callback( callback ),
			InvokeBase("Invoke<LPFN_OOO>")
	{
	}

	PyObject* Callback(PyObject* self, PyObject* args)
	{
		PyObject* O[3];
    
		if (!PyArg_ParseTuple(args, "OOO", &O[0], &O[1], &O[2]))
			return NULL;

		PythonObject p[3] = {
			PythonObject(O[0]),
			PythonObject(O[1]),
			PythonObject(O[2]),
		};

		PythonObject* result = m_callback(p[0], p[1], p[2]);
		Py_DECREF(O[0]);
		Py_DECREF(O[1]);
		Py_DECREF(O[2]);
		return CallbackResult(result);
	}

private:
	LPFN_OOO m_callback;
};

class PythonMemberFunctionCallback;

class NAPYR2_API PythonModule
    {
    public:
        PythonModule( LPCSTR name, LPCSTR docstring = "" );
        virtual ~PythonModule();
    
    private:
        PythonModule( const PythonModule& objectSrc );
        PythonModule& operator=( const PythonModule& objectSrc );

	public:
		void Define( LPCSTR name, const PythonObject& value );

		template <typename T> void Add(LPCSTR name, T func, LPCSTR docstring = "" )
		{
			printf("Add: %s\r\n", name);
			AddFunction(name, new Invoke<T>(func), docstring);
		};

		void AddFunction( LPCSTR name, PyCFunction function, LPCSTR docstring = "" );
		void AddFunction( LPCSTR name, InvokeBase* instance, LPCSTR docstring = "" );

    private:
		friend class PythonRuntime;

		std::vector<PythonMemberFunctionCallback*> m_allocated_methods;
		std::string m_name;
		std::string m_docstring;
		std::vector<PyMethodDef> m_methods;
		std::map<std::string, PyObject*> m_defines;
		PyObject* m_pyModule;
    };

#pragma warning( pop )
} // namespace napyr2 ends here

#endif // napyr2_Call_H
